#include "ParticleSystem.h"

ParticleSystem::ParticleSystem(void)
{
	gameTime  = 0;
	firstTime = true;
	g_pDeviceStats = 0;
}

ParticleSystem::~ParticleSystem(void)
{
}

void ParticleSystem::Init(void)
{

	numParticles = 10;
	initialParticles = (ParticleVS*) malloc(sizeof(ParticleVS) * MAX_VERTICES );
	memset(initialParticles ,0,sizeof(ParticleVS) * MAX_VERTICES );

	for(int i = 0 ; i < numParticles ; i++)
	{
		initialParticles[i].age = 0;
		initialParticles[i].sizeW  = XMFLOAT2(5,5);		
		initialParticles[i].type = 0;
		initialParticles[i].initialPosW = XMFLOAT3(0,0,0);
		initialParticles[i].initialVelW = XMFLOAT3(0,0,0);
	}

	HRESULT result;
	D3D11_BUFFER_DESC vbd;
	vbd.Usage = D3D11_USAGE_DEFAULT;
	vbd.ByteWidth = sizeof(ParticleVS) * MAX_VERTICES;
	vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT;
	vbd.CPUAccessFlags = 0;
	vbd.MiscFlags = 0;
	result = EngineStuff::EGraphicsDevice->CreateBuffer(&vbd, 0, &mStreamOutVB);
	CheckErrorVoid(result);

	D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory( &InitData, sizeof(InitData) );
    InitData.pSysMem = initialParticles;
	InitData.SysMemPitch = sizeof(ParticleVS) * MAX_VERTICES ;
	result = EngineStuff::EGraphicsDevice->CreateBuffer(&vbd, &InitData, &mParticleBuffer);
	CheckErrorVoid(result);	

	delete [] initialParticles ;
	initialParticles = NULL;
	
	vbd.Usage = D3D11_USAGE_STAGING;
	vbd.ByteWidth = sizeof(ParticleVS) * MAX_VERTICES;
	vbd.BindFlags = 0;
	vbd.CPUAccessFlags = D3D10_CPU_ACCESS_READ;
	vbd.MiscFlags = 0;
	result = EngineStuff::EGraphicsDevice->CreateBuffer(&vbd, 0, &g_pStagingStreamOutBuffer);
	CheckErrorVoid(result);
	
	D3D11_INPUT_ELEMENT_DESC ptnl[] = 
	{
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
			{ "VELOCITY", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
			{ "SIZE", 0,     DXGI_FORMAT_R32G32_FLOAT,    0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
			{ "AGE", 0,      DXGI_FORMAT_R32_FLOAT      , 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 },
			{ "TYPE", 0,     DXGI_FORMAT_R32_UINT       , 0, 36, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	};	
	UINT size = ARRAYSIZE(ptnl);

	stride = sizeof( ParticleVS );

	ID3DBlob* pVSBlob = NULL;
    result = EngineStuff::EAssetManagment->CompileShaderFromFile("particleshader.fx","StreamOutVS","vs_4_0",&pVSBlob );
	CheckErrorVoid(result);
	
	result = EngineStuff::EGraphicsDevice->CreateVertexShader( pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &g_pVertexShader );
    if( FAILED( result ) )
    {    
        pVSBlob->Release();
        return ;
    }	

    // Create the input layout
	result = EngineStuff::EGraphicsDevice->CreateInputLayout(ptnl, size, pVSBlob->GetBufferPointer(),
                                          pVSBlob->GetBufferSize(), &g_pVertexLayout );
    pVSBlob->Release();
    CheckErrorVoid(result);

	D3D11_SO_DECLARATION_ENTRY soDeclaration[] = 
	{
		{ 0, "POSITION", 0, 0, 3, 0 },		
		{ 0, "VELOCITY", 0, 0, 3, 0 },
		{ 0, "SIZE", 0, 0, 2, 0 },
		{ 0, "AGE", 0, 0, 1, 0 },
		{ 0, "TYPE", 0, 0, 1, 0 }, 		
	};
	
	///create and compile shaders	
	UINT strides[1];
	strides[0] = sizeof(float) * 9 + sizeof(UINT) * 1;
	SOstride = strides[0];

	// Compile the Geometry shader
	ID3DBlob* pGSBlob = NULL;
	result = EngineStuff::EAssetManagment->CompileShaderFromFile( "particleshader.fx", "StreamOutGS", "gs_4_0", &pGSBlob  );		
	CheckErrorVoid(result);

	// Create the Geometry shader
	result = EngineStuff::EGraphicsDevice->CreateGeometryShaderWithStreamOutput( pGSBlob->GetBufferPointer(), pGSBlob->GetBufferSize(), soDeclaration ,5, strides ,1,0,NULL, &g_pGeometricShader);
	if( FAILED( result ) )
	{    
		pGSBlob->Release();
		return ;
	}	

	// Create the constant buffers
	D3D11_BUFFER_DESC bd;
    ZeroMemory( &bd, sizeof(bd) );
    bd.Usage = D3D11_USAGE_DEFAULT;                    
    bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    bd.CPUAccessFlags = 0;    
    bd.ByteWidth = sizeof(cteBuffer);	
	result = EngineStuff::EGraphicsDevice->CreateBuffer( &bd, NULL, &dataBuffer);
	CheckErrorVoid(result);

	mRandomTexRV  = EngineStuff::EAssetManagment->RandomTexture1D(1024);
	particleTexRV = EngineStuff::EAssetManagment->LoadTexture("C:/Users/tpastor/Desktop/Release/Code/PloobsEngine/Assets/Content/Textures/smoke.png");
	//particleTexRV = EngineStuff::EAssetManagment->ColorTexture(500,500,0.5f,0.5f,0.5f,0.5f);	
	

	// Create the sample state
    D3D11_SAMPLER_DESC sampDesc;
    ZeroMemory( &sampDesc, sizeof(sampDesc) );
    sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    sampDesc.MinLOD = 0;
    sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
	result = EngineStuff::EGraphicsDevice->CreateSamplerState( &sampDesc, &g_pSamplerLinear );
    CheckErrorVoid(result);

	D3D11_DEPTH_STENCIL_DESC descDS;
	ZeroMemory(&descDS, sizeof(descDS));
	descDS.DepthEnable = false;
	descDS.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;	
	descDS.StencilEnable = false;	
	result = EngineStuff::EGraphicsDevice->CreateDepthStencilState( &descDS, &g_DepthState);
	CheckErrorVoid(result);

#if defined _DEBUG && defined DEEPDEBUG  
	D3D11_QUERY_DESC queryDesc;
	queryDesc.Query = D3D11_QUERY_PIPELINE_STATISTICS;
	queryDesc.MiscFlags = 0;
	result = EngineStuff::EGraphicsDevice->CreateQuery( &queryDesc, &g_pDeviceStats ) ;
	CheckErrorVoid(result);
#endif

	pVSBlob = NULL;
    result = EngineStuff::EAssetManagment->CompileShaderFromFile("particle1.fx","DrawVS","vs_4_0",&pVSBlob );
	CheckErrorVoid(result);
	
	result = EngineStuff::EGraphicsDevice->CreateVertexShader( pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &DVertexShader );
    if( FAILED( result ) )
    {    
        pVSBlob->Release();
        return ;
    }	

	// Compile the Geometry shader
	pGSBlob = NULL;
	result = EngineStuff::EAssetManagment->CompileShaderFromFile( "particle1.fx", "DrawGS", "gs_4_0", &pGSBlob  );		
	CheckErrorVoid(result);

	// Create the Geometry shader
	result = EngineStuff::EGraphicsDevice->CreateGeometryShader( pGSBlob->GetBufferPointer(), pGSBlob->GetBufferSize(), NULL, &DGeometricShader);
	if( FAILED( result ) )
	{    
		pGSBlob->Release();
		return ;
	}

	ID3DBlob* pPSBlob = NULL;
	result = EngineStuff::EAssetManagment->CompileShaderFromFile( "particle1.fx", "DrawPS", "ps_4_0", &pPSBlob );		
	CheckErrorVoid(result);

	// Create the PIXEL shader
	result = EngineStuff::EGraphicsDevice->CreatePixelShader( pPSBlob ->GetBufferPointer(), pPSBlob ->GetBufferSize(), NULL, &DPixelShader );
	if( FAILED( result ) )
	{    
		pGSBlob->Release();
		return ;
	}

	D3D11_BLEND_DESC blendDesc;
	ZeroMemory(&blendDesc, sizeof(D3D11_BLEND_DESC) );
    blendDesc.AlphaToCoverageEnable = false;
    blendDesc.IndependentBlendEnable = false;        
    blendDesc.RenderTarget[0].BlendEnable = true;
	blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
	blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
	/*blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
	blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_ONE;*/
	blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;

	blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_SRC_ALPHA;
	blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
	blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;

	blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL ;

	result = EngineStuff::EGraphicsDevice->CreateBlendState(&blendDesc, &blendState);
	CheckErrorVoid(result);

}

void ParticleSystem::update(UINT dt,Camera* camera)
{
	float timeElapsed = dt/1000.0f;
	gameTime += timeElapsed ;
	XMStoreFloat4x4(&datacpu.gViewProj ,XMMatrixTranspose( camera->getViewMatrix() * camera->getProjectionMatrix() ));
	XMStoreFloat4(&datacpu.gEyePosW,camera->GetPosition());	
	datacpu.AditionalInfos = XMFLOAT4(gameTime,timeElapsed,30,0);	
	datacpu.gEmitDirW = XMFLOAT4(0,0,0,0);
	datacpu.gEmitPosW = XMFLOAT4(0,0,0,0);
}

void ParticleSystem::draw()
{
	EngineStuff::EImediateDeviceContext->IASetInputLayout( g_pVertexLayout );
	EngineStuff::EImediateDeviceContext->VSSetShader( g_pVertexShader, NULL, 0 );    
    EngineStuff::EImediateDeviceContext->PSSetShader( NULL, NULL, 0 );
	EngineStuff::EImediateDeviceContext->GSSetShader( g_pGeometricShader , NULL, 0 );

	EngineStuff::PushDepthState(g_DepthState,1);

	EngineStuff::EImediateDeviceContext->UpdateSubresource( dataBuffer, 0, NULL, &datacpu, 0, 0 );
	EngineStuff::EImediateDeviceContext->GSSetConstantBuffers( 0, 1, &dataBuffer );
		
	EngineStuff::EImediateDeviceContext->GSSetShaderResources(0, 1, &mRandomTexRV );
    EngineStuff::EImediateDeviceContext->GSSetSamplers( 0, 1, &g_pSamplerLinear );

	UINT off[1];
	off[0] = 0;
	EngineStuff::EImediateDeviceContext->SOSetTargets(1,&mStreamOutVB,off);
	
    UINT offset = 0;		
	EngineStuff::EImediateDeviceContext->IASetVertexBuffers( 0, 1, &mParticleBuffer, &stride, &offset );
	    
	EngineStuff::EImediateDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);

	///begin draw !!!
#if defined _DEBUG && defined DEEPDEBUG  
	EngineStuff::EImediateDeviceContext->Begin(g_pDeviceStats);
#endif
	if(firstTime)
	{
		EngineStuff::EImediateDeviceContext->Draw(numParticles,0);
		firstTime = false;
	}
	else
	{
		EngineStuff::EImediateDeviceContext->DrawAuto();
	}
#if defined _DEBUG && defined DEEPDEBUG  
	EngineStuff::EImediateDeviceContext->End(g_pDeviceStats);
#endif
	///end draw

	ID3D11Buffer* nullbuffer[1] = { NULL };		
	EngineStuff::EImediateDeviceContext->SOSetTargets( 1, nullbuffer, off);		
	
	EngineStuff::PopDepthState();

#if defined _DEBUG && defined DEEPDEBUG  
	///READBACK AND PRINT INFO		
	D3D11_QUERY_DATA_PIPELINE_STATISTICS stats;
	while( S_OK != EngineStuff::EImediateDeviceContext->GetData(g_pDeviceStats, &stats, g_pDeviceStats->GetDataSize(), 0 ) );
	
	EngineStuff::EImediateDeviceContext->CopyResource( g_pStagingStreamOutBuffer, mStreamOutVB );
	D3D11_MAPPED_SUBRESOURCE data;
	if( SUCCEEDED( EngineStuff::EImediateDeviceContext->Map( g_pStagingStreamOutBuffer, 0, D3D11_MAP_READ, 0, &data ) ) )
	{		
		ParticleVS *pRaw = reinterpret_cast< ParticleVS* >( data.pData );	///nunca achei q ia usar reinterpret_cast na vida !!!!

		for(int i  = 0 ; i < stats.GSPrimitives; i++)
		{	
			char* arr = new char[100];
			sprintf(arr,"PARTICLE %d \n",i);	
			OutputDebugString(arr);						
			sprintf(arr,"Type : %d \n",pRaw[i].type);			 
			OutputDebugString(arr);
			sprintf(arr,"Age : %f \n",pRaw[i].age );			 
			OutputDebugString(arr);
			sprintf(arr,"Size : x %f y %f \n",pRaw[i].sizeW.x ,pRaw[i].sizeW.y );			 
			OutputDebugString(arr);
			sprintf(arr,"InitialPosW : x %f y %f z %f \n",pRaw[i].initialPosW.x ,pRaw[i].initialPosW.y ,pRaw[i].initialPosW.z  );			 
			OutputDebugString(arr);
			sprintf(arr,"InitialVelW : x %f y %f z %f \n",pRaw[i].initialVelW.x ,pRaw[i].initialVelW.y ,pRaw[i].initialVelW.z  );			 
			OutputDebugString(arr);
			OutputDebugString("\n");
			delete  [] arr;
		}		
		OutputDebugString("------------------------\n");
		EngineStuff::EImediateDeviceContext->Unmap( g_pStagingStreamOutBuffer, 0 );
	}
#endif


    std::swap(mParticleBuffer, mStreamOutVB);
	
	EngineStuff::PushBlenderState(blendState,NULL,0xffffffff); 	

	EngineStuff::EImediateDeviceContext->IASetVertexBuffers( 0, 1, &mParticleBuffer, &stride, &offset );
	
	EngineStuff::EImediateDeviceContext->VSSetShader( DVertexShader, NULL, 0 );    
	EngineStuff::EImediateDeviceContext->PSSetShader( DPixelShader, NULL, 0 );
	EngineStuff::EImediateDeviceContext->GSSetShader( DGeometricShader , NULL, 0 );
	
	EngineStuff::EImediateDeviceContext->GSSetConstantBuffers( 0, 1, &dataBuffer );		
	EngineStuff::EImediateDeviceContext->PSSetShaderResources(0, 1, &particleTexRV  );
    EngineStuff::EImediateDeviceContext->PSSetSamplers( 0, 1, &g_pSamplerLinear );

	EngineStuff::EImediateDeviceContext->DrawAuto();

	EngineStuff::PopBlenderState(); 	
}